# game.py
"""Contains the Game class, which is the mode that handles
the actual gameplay.
"""

import math
import sys

import Box2D.b2 as b2
import pyglet
from pyglet.gl import *

import box
import camera
from camera import world_to_pixels
import config
from constants import UPDATE_RATE
import contact_listener
import debug
from debug import msg, SomethingIsWrongError
import mode
import player
import portal
import relation
import resources
import utils
import wall


class EndOfGame(Exception):
    pass


class Game(mode.Mode):
    name = "game"

    def __init__(self, level_name=None):
        self.init_world()
        self.init_graphics()

        try:
            if level_name is None:
                level_name = resources.get_current_level()
        except resources.EndOfGame:
            level_name = resources.all_levels()[-1]

        self.load_level(level_name)

        try:
            self.level.player
        except:
            utils.error(SomethingIsWrongError("Player is not defined in level \"%s\"" % self.level.name))

    def init_world(self):
        self.world = b2.world(gravity=(0, 0), doSleep=False)
        cl = contact_listener.ContactListener(self.world)
        self.world.contactListener = cl

        # A list of callbacks to call after the box2d.Step function
        # This is needed, for instance, because we cannot create
        # or destroy objects within the collision detection.
        # These functions take no arguments.
        self.world_poststep_cbs = []
        # The same, but with dubstep
        self.world_dubstep_cbs = []

        if config.debug:
            self.renderer = debug.PygletDraw(None)
            self.world.renderer = self.renderer

            self.renderer.flags = dict(
                drawShapes=True,
                drawJoints=True,
                drawAABBs=False,
                drawPairs=True,
                #drawCOMs  =True,
                # The following is only applicable when using b2DrawExtended.
                # It indicates that the C code should transform box2d coords to
                # screen coordinates.

                convertVertices=isinstance(self.renderer, b2.drawExtended))

            self.renderer.batch = pyglet.graphics.Batch()

    def init_graphics(self):
        glDisable(GL_DEPTH_TEST)
        glEnable(GL_BLEND)
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
        self.camera = camera.Camera((0, 0), scale=2)

        # The batches hold boxes/player instances and
        # walls/buttons/doors respectively
        self.batch_static = pyglet.graphics.Batch()
        self.batch_below_dynamic = pyglet.graphics.Batch()
        self.batch_dynamic = pyglet.graphics.Batch()
        self.batch_foreground = pyglet.graphics.Batch()

        self.background = pyglet.resource.image("background.jpg")
        self.background.anchor_x = self.background.width / 2
        self.background.anchor_y = self.background.height / 2

        self.fade_alpha = 0.0

    def load_level(self, filename):
        try:
            self.level_name = filename
            self.level = resources.load_level(self, filename)
        except IOError, e:
            utils.error(IOError("Could not load level \"%s\": %s" % (filename, e)))
            sys.exit(1)

    def fade(self, dt, duration, start_alpha, end_alpha, end_cb):
        """Fade screen linearly over duration seconds, from start_alpha to
        end_alpha. Then call end_cb."""

        step = (end_alpha - start_alpha)/float(duration) * dt
        if not (end_alpha > start_alpha and self.fade_alpha > end_alpha or
                end_alpha < start_alpha and self.fade_alpha < end_alpha):
            self.fade_alpha += step
        else:
            pyglet.clock.unschedule(self.fade)
            end_cb()

    def end_level(self, success=False):
        """End the level.

        success -- If True, increment the progress count and load the
        next level. If False, return to the menu.
        """

        def post_fade_end(success=success):
            if success:
                try:
                    resources.store_level_success()
                    self.control.switch_handler("level_transition",
                                                resources.get_current_level())
                except resources.EndOfGame:
                    sys.exit(0) # TODO: Some finishing screen here!
            else:
                self.control.switch_handler("menu")

        if success:
            duration = 1
        else:
            duration = 0.2

        pyglet.clock.schedule(self.fade, duration, self.fade_alpha, 1.0, post_fade_end)

    def connect(self, control):
        self.control = control
        self.window = control.window

        if config.debug:
            self.renderer.surface = self.window.screen

    def disconnect(self):
        del self.world

    def update(self, dt):

        #self.player.apply_hive_mind_collision()

        # Update player's instances
        self.level.player.update(dt)

        #self.camera.x, self.camera.y = map(camera.world_to_pixels, self.level.player.objects[0].body.position)

        # Update boxes' instances
        for box in self.level.boxes:
            box.update(dt)

        # Update buttons
        for electrical in self.level.electricals:
           electrical.update(dt)

        # The problem is that the impulses given are temporarily wrong here
        # and that the position is changed meanwhilst
        self.world.Step(1 / UPDATE_RATE, 10, 10)
        for f in self.world_poststep_cbs:
            f()

        self.world_poststep_cbs = []

        self.world.ClearForces()

    def on_key_press(self, key, mods):
        self.level.player.on_key_press(key, mods)
        if key == pyglet.window.key.R:
            self.control.switch_handler("level_transition", self.level_name)
        if key == pyglet.window.key.MINUS:
            self.camera.scale *= 1.2
        if key == pyglet.window.key.PLUS:
            self.camera.scale /= 1.2
        if key == pyglet.window.key.F11:
            self.window.set_fullscreen(not self.window.fullscreen)
        if key == pyglet.window.key.ESCAPE:
            self.end_level()
            return True

    def on_key_release(self, key, mods):
        self.level.player.on_key_release(key, mods)

    def on_draw(self):
        self.window.clear()
        self.camera.focus(self.window.width, self.window.height)

        glColor3f(1.0, 1.0, 1.0)
        self.background.blit(0, 0)

        glLineWidth(3)

        portal.draw_connections(self.level.portal_connections)

        self.batch_static.draw()
        self.batch_below_dynamic.draw()
        self.batch_dynamic.draw()
        self.batch_foreground.draw()

        for wall in self.level.walls:
            wall.draw()

        glLineWidth(3)
        for p in self.level.portals:
            p.draw()


        # Fade in/out effect
        glMatrixMode(GL_MODELVIEW)
        glPushMatrix()
        glLoadIdentity()
        glMatrixMode(GL_PROJECTION)
        glPushMatrix()
        glLoadIdentity()
        pyglet.graphics.draw(4, GL_QUADS,
                             ("v3i", (-1, -1, -1,
                                       1, -1, -1,
                                       1,  1, -1,
                                      -1,  1, -1)),
                             ("c4f", (0.0, 0.0, 0.0, self.fade_alpha) * 4))
        glPopMatrix()
        glMatrixMode(GL_MODELVIEW)
        glPopMatrix()

        if config.debug:
            #glLineWidth(1)

            glMatrixMode(GL_MODELVIEW)
            glPushMatrix()
            glMatrixMode(GL_PROJECTION)
            glPushMatrix()

            self.renderer.batch.draw()

            glPopMatrix()
            glMatrixMode(GL_MODELVIEW)
            glPopMatrix()


mode.modes["game"] = Game
